var Options = function () {
    this.pointLightDirection_X = 0.5;
    this.pointLightDirection_Y = 0;
    this.pointLightDirection_Z = 1.5;

    this.lightColor_R = 0.5;
    this.lightColor_G = 0.5;
    this.lightColor_B = 0.5;

    this.ambientLight_R = 0.1;
    this.ambientLight_G = 0.1;
    this.ambientLight_B = 0.1;

    this.currentAngleX = 66;
    this.currentAngleY = 0;
    this.currentAngleZ = 0;

    this.mesh = 1;
    this.shader = 2;
};

const gui = new dat.GUI();
var options = new Options();

gui.add(options, 'pointLightDirection_X', -5, 5).step(0.01);
gui.add(options, 'pointLightDirection_Y', -5, 5).step(0.01);
gui.add(options, 'pointLightDirection_Z', -5, 5).step(0.01);

gui.add(options, 'lightColor_R', 0, 1).step(0.01);
gui.add(options, 'lightColor_G', 0, 1).step(0.01);
gui.add(options, 'lightColor_B', 0, 1).step(0.01);

gui.add(options, 'ambientLight_R', 0, 1).step(0.01);
gui.add(options, 'ambientLight_G', 0, 1).step(0.01);
gui.add(options, 'ambientLight_B', 0, 1).step(0.01);

gui.add(options, 'currentAngleX', 0, 360).step(1);
gui.add(options, 'currentAngleY', 0, 360).step(1);
gui.add(options, 'currentAngleZ', 0, 360).step(1);


var meshController = gui.add(options, 'mesh', {cube: 1, sphere: 2});
meshController.onChange(function (value) {
    console.log("onChange:" + value)
    window.cancelAnimationFrame(myReq)
    main();
});

var shaderController = gui.add(options, 'shader', {lambert: 1, lambert_perfrag: 2, half_lambert_perfrag: 3});
shaderController.onChange(function (value) {
    window.cancelAnimationFrame(myReq)
    main();
});

var myReq;

var VSHADER_SOURCE =
    `
    attribute vec4 a_Position;
    uniform mat4 u_MvpMatrix;
    
    void main() {
        gl_Position = u_MvpMatrix * a_Position;
    }
    `

var FSHADER_SOURCE =
    `
    #ifdef GL_ES
    precision mediump float;
    #endif
    void main() {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);;
    }
    `;

var VSHADER_SOURCE_PERFRAG =
    `
    attribute vec4 a_Position;
    attribute vec4 a_Normal;
    attribute vec2 a_TexCoord;
    uniform mat4 u_MvpMatrix;
    uniform mat4 u_ModelMatrix;
    uniform mat4 u_NormalMatrix;
    uniform vec3 u_viewWorldPosition;
    varying vec4 v_Color;
    varying vec3 v_Normal;
    varying vec3 v_Position;
    varying vec2 v_TexCoord;
    varying vec3 v_surfaceToView;
    void main()
    {
        vec4
        color = vec4(1.0, 1.0, 1.0, 1.0);
        gl_Position = u_MvpMatrix * a_Position;
        v_TexCoord = a_TexCoord;
        v_Position = vec3(u_ModelMatrix * a_Position);
        v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));
        v_Color = color;
        v_surfaceToView = u_viewWorldPosition - v_Position;
    }
`
var FSHADER_SOURCE_PERFRAG =
    `#ifdef GL_ES
    precision mediump float;
    #endif
    uniform vec3 u_LightColor;
    uniform vec3 u_LightPosition;
    uniform vec3 u_AmbientLight;
    uniform sampler2D u_Sampler;
    varying vec3 v_Normal;
    varying vec3 v_Position;
    varying vec4 v_Color;
    varying vec2 v_TexCoord;
    varying vec3 v_surfaceToView;
    void main() {
      vec3 normal = normalize(v_Normal);
      vec3 lightDirection = normalize(u_LightPosition - v_Position);
      
      vec3 surfaceToViewDirection = normalize(v_surfaceToView);
      vec3 halfVector = normalize(lightDirection + surfaceToViewDirection);
      
      float specular = 0.0;
      
      float light = dot(normal, lightDirection);
      if (light > 0.0) {
        specular = pow(dot(normal, halfVector), 100.0);
      }
      
      float nDotL = max(dot(lightDirection, normal), 0.0);
      vec4 texColor = texture2D(u_Sampler, v_TexCoord);
      vec3 diffuse = u_LightColor * texColor.rgb * nDotL;
      vec3 ambient = u_AmbientLight * texColor.rgb;
      gl_FragColor = vec4(diffuse + ambient, v_Color.a);
      gl_FragColor.rgb += specular;
    }
      `

var FSHADER_SOURCE_PERFRAG_HALF =
    `#ifdef GL_ES
    precision mediump float;
    #endif
    uniform vec3 u_LightColor;
    uniform vec3 u_LightPosition;
    uniform vec3 u_AmbientLight;
    uniform sampler2D u_Sampler;
    varying vec3 v_Normal;
    varying vec3 v_Position;
    varying vec4 v_Color;
    varying vec2 v_TexCoord;
    void main() {
      vec3 normal = normalize(v_Normal);
      vec3 lightDirection = normalize(u_LightPosition - v_Position);
      float nDotL = max(dot(lightDirection, normal) * 0.5 + 0.5, 0.0);
      vec4 texColor = texture2D(u_Sampler, v_TexCoord);
      vec3 diffuse = u_LightColor * texColor.rgb * nDotL;
      vec3 ambient = u_AmbientLight * texColor.rgb;
      gl_FragColor = vec4(diffuse + ambient, v_Color.a);
    }
      `
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);

var program_light = createProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
var program_cube = createProgram(gl, VSHADER_SOURCE_PERFRAG, FSHADER_SOURCE_PERFRAG);

function main() {
    function step(timestamp) {
        draw(gl, canvas);
        myReq = window.requestAnimationFrame(step);
    }

    myReq = window.requestAnimationFrame(step)
}

function draw(gl, canvas) {
    drawCube(gl, canvas)
    drawLightCube(gl, canvas);
}

function drawCube(gl, canvas) {
    gl.useProgram(program_cube);
    gl.program = program_cube;

    if (!initTextures(gl)) {
        console.log('Failed to intialize the texture.');
        return;
    }

    var n = initVertexBuffersCube(gl);

    gl.clearColor(0, 0, 0, 1);
    gl.enable(gl.DEPTH_TEST);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
    var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
    var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
    var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
    // var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
    var u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');
    var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
    var u_viewWorldPosition = gl.getUniformLocation(gl.program, 'u_viewWorldPosition');

    if (!u_ModelMatrix || !u_MvpMatrix || !u_NormalMatrix || !u_LightColor || !u_LightPosition || !u_AmbientLight) {
        console.log('Failed to get the storage location');
        return;
    }

    let pointLightDirection_X = options.pointLightDirection_X;
    let pointLightDirection_Y = options.pointLightDirection_Y;
    let pointLightDirection_Z = options.pointLightDirection_Z;

    let lightColor_R = options.lightColor_R;
    let lightColor_G = options.lightColor_G;
    let lightColor_B = options.lightColor_B;

    let ambientLight_R = options.ambientLight_R;
    let ambientLight_G = options.ambientLight_G;
    let ambientLight_B = options.ambientLight_B;

    let currentAngleX = options.currentAngleX;
    let currentAngleY = options.currentAngleY;
    let currentAngleZ = options.currentAngleZ;

    gl.uniform3f(u_LightColor, lightColor_R, lightColor_G, lightColor_B);
    gl.uniform3f(u_LightPosition, pointLightDirection_X, pointLightDirection_Y, pointLightDirection_Z);
    gl.uniform3f(u_AmbientLight, ambientLight_R, ambientLight_G, ambientLight_B);

    var modelMatrix = new Matrix4();
    var mvpMatrix = new Matrix4();
    var normalMatrix = new Matrix4();

    modelMatrix.scale(0.5, 0.5, 0.5).rotate(currentAngleX, 1, 0, 0).rotate(currentAngleY, 0, 1, 0).rotate(currentAngleZ, 0, 0, 1);

    mvpMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100);
    mvpMatrix.lookAt(0, 0, 6, 0, 0, 0, 0, 1, 0);
    // mvpMatrix.lookAt(0, 0, 6, 0, 0, 0, 0, 1, 0);
    mvpMatrix.multiply(modelMatrix);

    normalMatrix.setInverseOf(modelMatrix);
    normalMatrix.transpose();

    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
    gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);
    gl.uniform3fv(u_viewWorldPosition, [0, 0, 6]);

    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function drawLightCube(gl, canvas, type) {
    gl.useProgram(program_light);
    gl.program = program_light;

    var n = initVertexBuffersLightCube(gl);

    var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');

    let pointLightDirection_X = options.pointLightDirection_X;
    let pointLightDirection_Y = options.pointLightDirection_Y;
    let pointLightDirection_Z = options.pointLightDirection_Z;

    var modelMatrix = new Matrix4();
    var mvpMatrix = new Matrix4();
    var normalMatrix = new Matrix4();

    modelMatrix.translate(pointLightDirection_X, pointLightDirection_Y, pointLightDirection_Z).scale(0.1, 0.1, 0.1);

    mvpMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100);
    mvpMatrix.lookAt(0, 0, 6, 0, 0, 0, 0, 1, 0);
    mvpMatrix.multiply(modelMatrix);

    normalMatrix.setInverseOf(modelMatrix);
    normalMatrix.transpose();

    gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function initTextures(gl) {
    var texture = gl.createTexture();
    if (!texture) {
        console.log('Failed to create the texture object');
        return false;
    }

    var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
    if (!u_Sampler) {
        console.log('Failed to get the storage location of u_Sampler');
        return false;
    }
    loadTexture(gl, texture, u_Sampler, image);
    return true;
}

function loadTexture(gl, texture, u_Sampler, image) {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture);

    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);

    gl.uniform1i(u_Sampler, 0);
}

function initVertexBuffersCube(gl) {
    var vertices = new Float32Array([
        1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0,
        1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0,
        1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0,
        -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0,
        -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,
        1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0
    ]);

    var normals = new Float32Array([
        0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
        1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
        -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
        0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0,
        0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0
    ]);

    var verticesTexCoords = new Float32Array([
        // Vertex coordinates, texture coordinate
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
    ]);

    var indices = new Uint8Array([
        0, 1, 2, 0, 2, 3,
        4, 5, 6, 4, 6, 7,
        8, 9, 10, 8, 10, 11,
        12, 13, 14, 12, 14, 15,
        16, 17, 18, 16, 18, 19,
        20, 21, 22, 20, 22, 23
    ]);

    if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;
    // if (!initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT)) return -1;
    if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;
    if (!initArrayBuffer(gl, 'a_TexCoord', verticesTexCoords, 2, gl.FLOAT)) return -1;

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    var indexBuffer = gl.createBuffer();
    if (!indexBuffer) {
        console.log('Failed to create the buffer object');
        return false;
    }

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

    return indices.length;
}

function initVertexBuffersLightCube(gl) {
    var vertices = new Float32Array([
        1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0,
        1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0,
        1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0,
        -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0,
        -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,
        1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0
    ]);

    var indices = new Uint8Array([
        0, 1, 2, 0, 2, 3,
        4, 5, 6, 4, 6, 7,
        8, 9, 10, 8, 10, 11,
        12, 13, 14, 12, 14, 15,
        16, 17, 18, 16, 18, 19,
        20, 21, 22, 20, 22, 23
    ]);

    if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    var indexBuffer = gl.createBuffer();
    if (!indexBuffer) {
        console.log('Failed to create the buffer object');
        return false;
    }

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

    return indices.length;
}

function initVertexBuffersSphere(gl) {
    var SPHERE_DIV = 13;

    var i, ai, si, ci;
    var j, aj, sj, cj;
    var p1, p2;

    var positions = [];
    var indices = [];

    for (j = 0; j <= SPHERE_DIV; j++) {
        aj = j * Math.PI / SPHERE_DIV;
        sj = Math.sin(aj);
        cj = Math.cos(aj);
        for (i = 0; i <= SPHERE_DIV; i++) {
            ai = i * 2 * Math.PI / SPHERE_DIV;
            si = Math.sin(ai);
            ci = Math.cos(ai);

            positions.push(si * sj);  // X
            positions.push(cj);       // Y
            positions.push(ci * sj);  // Z
        }
    }

    for (j = 0; j < SPHERE_DIV; j++) {
        for (i = 0; i < SPHERE_DIV; i++) {
            p1 = j * (SPHERE_DIV + 1) + i;
            p2 = p1 + (SPHERE_DIV + 1);

            indices.push(p1);
            indices.push(p2);
            indices.push(p1 + 1);

            indices.push(p1 + 1);
            indices.push(p2);
            indices.push(p2 + 1);
        }
    }

    if (!initArrayBuffer(gl, 'a_Position', new Float32Array(positions), 3, gl.FLOAT)) return -1;
    if (!initArrayBuffer(gl, 'a_Normal', new Float32Array(positions), 3, gl.FLOAT)) return -1;

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    var indexBuffer = gl.createBuffer();
    if (!indexBuffer) {
        console.log('Failed to create the buffer object');
        return -1;
    }
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

    return indices.length;
}

function initArrayBuffer(gl, attribute, data, num, type) {
    var buffer = gl.createBuffer();
    if (!buffer) {
        console.log('Failed to create the buffer object');
        return false;
    }
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
    var a_attribute = gl.getAttribLocation(gl.program, attribute);
    if (a_attribute < 0) {
        console.log('Failed to get the storage location of ' + attribute);
        return false;
    }
    gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
    gl.enableVertexAttribArray(a_attribute);

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    return true;
}

var image = new Image();
image.onload = function () {
    main();
};
image.src = './sky.jpg';


